#include "Query.h"
#include "../utils/Functions.h"
#include <stdlib.h>
#include "../compresores/CodigoDelta.h"

using std::vector;
using std::ifstream;
using std::ofstream;
using std::cout;
using std::endl;
using std::ios;
using std::filebuf;

Query::Query(string rutaCarpetaIdx) {
	string paraDocData(rutaCarpetaIdx+DOC_DATA);
	string paraDocFC(rutaCarpetaIdx+DOC_F_C);
	string paraDocWords(rutaCarpetaIdx+DOC_WORDS_POINTERS);
	string paraNamesTable(rutaCarpetaIdx+DOC_NAMES_TABLE);
	wordsNumber = 0;
	v_registers = new vector <Register> ();
//	this->indexFile = new ifstream (indexFile.c_str(), ios::in | ios::binary);
	dataFile = new ifstream (paraDocData.c_str(), ios::in | ios::binary);
	FCFile = new ifstream (paraDocFC.c_str(), ios::in | ios::binary);
	pointersFile = new ifstream (paraDocWords.c_str(), ios::in | ios::binary);
	namesTable = new ifstream(paraNamesTable.c_str(), ios::in | ios::binary);

	//todo controlar si no puedo abrir algun archivo. y terminar la ejecucion del programa o algo.
	if(pointersFile == NULL) {
		cout << "pointers file es null" << endl;
		exit(2);
	}
	pointersFile->seekg(0, pointersFile->beg);
	pointerData = 0;
	pointerWord = 0;
	pointersFile->read (reinterpret_cast <char*> (&wordsNumber), sizeof (unsigned long int));
	if (DEBUG > 0)
		cout << "------- cantidad de terminos a leer: " << wordsNumber << endl;
}

Query::~Query() {
	delete v_registers;
	delete dataFile;
	delete FCFile;
	delete pointersFile;
	delete namesTable;
}

int cmp_t2 (const void* arg1, const void* arg2) {
	if (((QueryToken*)arg1)->v_data->size() > ((QueryToken*)arg2)->v_data->size()) return 1;
	if (((QueryToken*)arg2)->v_data->size() > ((QueryToken*)arg1)->v_data->size()) return -1;
	return 0;
}

vector <Match>* Query::resolveQuery (vector<QueryToken>* v_query){
	unsigned int i, j, k, docActual;
	bool cumpleDoc, tokenCumpleDoc;

	vector <Match>* v_matchs = new vector<Match>();
	if(v_query != NULL && v_query->size() > 0) {
		vector<Data>* docCandidates = new vector<Data> ();

		qsort ((void*)&(v_query->at(0)), v_query->size(), sizeof(QueryToken), cmp_t2); //ordeno la consulta por cant minima de doc

		for (i=0; i<v_query->at(0).v_data->size(); i++) {                 //itera sobre los documentos
			docActual = v_query->at(0).v_data->at(i).document;
			cumpleDoc = true;

			// buscar que todos tengan este doc
			for (j=1; j<v_query->size() && cumpleDoc; j++) {
				tokenCumpleDoc = false;
				for (k=0; k<v_query->at(j).v_data->size() ; k++) {
					if(v_query->at(j).v_data->at(k).document == docActual){
						// restamos o sumamos la diferencia de posiciones respecto de la menor
						v_query->at(j).v_data->at(k).pos->at(0) -= v_query->at(j).pos;
						tokenCumpleDoc = true;
						break;
					}

				}
				cumpleDoc = cumpleDoc && tokenCumpleDoc;
			}

			if(cumpleDoc) {
				docCandidates->push_back(v_query->at(0).v_data->at(i));
				// restamos o sumamos la diferencia de posiciones respecto de la menor
				v_query->at(0).v_data->at(i).pos->at(0) -= v_query->at(0).pos;
			}
		}


		vector <unsigned int >* v_posiciones_primer_termino;// = new vector <unsigned int>();
		vector <unsigned int >* v_posiciones_siguiente_termino;// = new vector <unsigned int>();

		int result;
		for (i=0; i<docCandidates->size(); i++) {
			Match match;
			docActual = docCandidates->at(i).document;
			match.document = docActual;
			match.long_match = 0; // guardo la cantidad de veces que aparece completa la frase.
			match.fraseCompleta = false;
			v_posiciones_primer_termino = getPosiciones(v_query->at(0).v_data, docActual);
			if(v_posiciones_primer_termino != NULL){
				for (k=0; k< v_posiciones_primer_termino->size(); k++){
					unsigned int l_match = 0;
					for (j=1; j< v_query->size();j++){
						v_posiciones_siguiente_termino = getPosiciones(v_query->at(j).v_data, docActual);
						if(v_posiciones_siguiente_termino != NULL){
							result = binarySearch(v_posiciones_primer_termino->at(k), v_posiciones_siguiente_termino);
							if (result != -1){
								l_match++;
							}
							v_posiciones_siguiente_termino->clear();
						}
					}
					if (l_match == (v_query->size()-1)){
						match.fraseCompleta = true;
						match.long_match++;
					}

				} //fin for (k=0; k< v_posiciones_primer_termino->size(); k++){
			}
			if (match.fraseCompleta){
				v_matchs->push_back(match);
			}
		}
		if(v_posiciones_primer_termino != NULL)
			v_posiciones_primer_termino->clear();

		cleanVectorData(docCandidates);
		delete docCandidates;
	}

	return v_matchs;
}

vector <unsigned int>* Query::getPosiciones(vector<Data>* v_data, int document){
	unsigned int k,j;
	vector <unsigned int>* result = new vector<unsigned int>();
	vector <unsigned int>* posAux;// = new vector<unsigned int>();
	// buscar que todos tengan este doc
	for (k=0; k<v_data->size(); k++) {
		if(v_data->at(k).document == (unsigned int)document){
			posAux = v_data->at(k).pos;
			result->push_back(posAux->at(0));
			for (j=1; j<posAux->size(); j++){
				result->push_back(result->at(j-1)+posAux->at(j));
			}
			return result;
		}
	}
	result->clear();
	delete result;
	return NULL;
}

void Query::fillDatas(vector<QueryToken>* query) {
	string token;

	if (query != NULL)
		for(unsigned int i=0; i<query->size(); i++) {
			cout << "\n";
			if(query->at(i).v_data == NULL) {
				token = query->at(i).word;
				query->at(i).v_data = busquedaBinaria(token);
				// falta terminar esta funcion

				if (query->at(i).v_data->size() == 0 ) {
					// Como no se encontro una palabra ver la forma de no devolver nada
				}
			}
		}
}

vector<Data>* Query::busquedaBinaria(string token) {
	vector<Data>* data = new vector<Data>();
//	filebuf* buffer = pointersFile.buffer;
	uint8_t offset = sizeof(unsigned int); // al principio se guarda la cantidad de palabras asique sumo esas posiciones.
	unsigned int piso = 0;
	unsigned int techo = (floor(wordsNumber/floor(log2(wordsNumber))) - 1);
	unsigned int medio = (techo + piso) / 2;
	char* buffer = new char[4];
	unsigned int pos;
	short int charsRepeated;
	short int charsNew;
	unsigned int pointToPos;
	string tokenSearched = string("");
	string lastTokenSearched = string("");
	string auxToken;
	char c;

	bool encontrado = false;
	unsigned int medioAnterior = medio + 1;
	int strCmp;
	// busqueda binaria hasta obtener el bloque en donde se deberia encontrar el termino
	while (piso < techo-1 && !encontrado && medio!=medioAnterior) {
		tokenSearched.clear();
		medioAnterior = medio;
		pointersFile->seekg((medio*4)+offset, pointersFile->beg); // multiplico por 4 porque cada puntero ocupa 4 bytes
		pointersFile->read(buffer, 4);
		if (!pointersFile->eof()) {

			pos = charToInt(buffer);
			FCFile->seekg(pos, FCFile->beg);
			FCFile->read(reinterpret_cast<char*>(&charsRepeated), sizeof(short int));
			if ( !FCFile->eof()) {
				FCFile->read(reinterpret_cast<char*>(&charsNew), sizeof(short int));
				//FIXME tratar de levantar el string de un saque.
				for (short int l=0; l<charsNew; l++) {
					FCFile->read(reinterpret_cast<char*>(&c), sizeof(char));
					tokenSearched.push_back(c);
				}

				strCmp = strcmp(token.c_str(), tokenSearched.c_str());
				if( strCmp < 0) {
					techo = medio - 1;
				} else if (strCmp > 0) {
					piso = medio;
				} else {
					encontrado = true;
				}
				medio = (techo+piso) / 2;
			}
		}

	}

	//busqueda secuencial dentro del bloque para ver si se encuentra el termino
	if(encontrado) {
		FCFile->read(buffer, sizeof(int));
		pointToPos = charToInt(buffer);
		dataFile->seekg(pointToPos, dataFile->beg);

		//levantar las posiciones
		data = getDataFromDataFile();
	} else {
	// leemos los bloques desde piso hasta techo ( deberian ser 2 bloques)

		pointersFile->seekg((piso*4)+offset, pointersFile->beg);//cada puntero ocupa 4 bytes
		pointersFile->read(buffer, sizeof(unsigned int));
		pos = charToInt(buffer);
		if (!pointersFile->eof()) // controlar bien el fin de archivo
			//todo: que hacer si es fin de archivo?

		FCFile->seekg(pos, FCFile->beg);
		bool noExiste = false;
		while (!FCFile->eof() && !encontrado && !noExiste) {
//			cout << "leyendo secuencialmente..." << endl;
			auxToken.clear();
			FCFile->read(reinterpret_cast<char*>(&charsRepeated), sizeof(short int));
			FCFile->read(reinterpret_cast<char*>(&charsNew), sizeof(short int));
			for (short int k=0; k<charsNew; k++) {
				FCFile->read(reinterpret_cast<char*>(&c), sizeof(char));
				auxToken.push_back(c);
			}
			FCFile->read(buffer, sizeof(int));
			pointToPos = charToInt(buffer);

			tokenSearched = lastTokenSearched.substr(0, charsRepeated);
			tokenSearched += auxToken;
			lastTokenSearched = tokenSearched;

//			cout << tokenSearched << endl;
			strCmp = strcmp(token.c_str(), tokenSearched.c_str());
			if(strCmp < 0) {
				noExiste = true;
			} else if (strCmp == 0) {
				dataFile->seekg(pointToPos, dataFile->beg);

				//levantar posiciones
				data = getDataFromDataFile();
				encontrado = true;
			}
		}
	}

	delete buffer;

	return data;
}

vector<Data>* Query::getDataFromDataFile() {
	vector<Data>* data = new vector<Data>();
	CodigoDelta* delta = new CodigoDelta();
	if(!dataFile->eof()) {
		data = delta->getDataFromFile(dataFile);
	}
	delete delta;
	return data;
}


void Query::printDocumentsNames(vector<unsigned int>* documents) {
	if(documents == NULL)
		cout << "No se encontraron resultados para su búsqueda."
				"Verifique haber ingresado las palabras correctamente." << endl;
	else {
		unsigned int docActual;
		unsigned int docAnterior = 0;
		int diff;
		unsigned short int docLeido;
		uint8_t sizeName;
		string name;
		namesTable->seekg(0,namesTable->beg); //me muevo al principio

		cout << "\nSu búsqueda se encuentra en los siguientes documentos:" << endl;
		for(unsigned int i=0; i<documents->size(); i++) {
			docActual = documents->at(i); // resto uno porque empezamos a contar los documentos desde el 1, no desde el 0
			diff = docActual - docAnterior;
			name = string("");
			if(diff<0) {
				namesTable->seekg(0,namesTable->beg); //me muevo al principio
				diff = docActual;
			}
			advanceNDocumentsNames(diff);
			namesTable->read(reinterpret_cast<char*>(&docLeido), sizeof(unsigned short int));
			namesTable->read(reinterpret_cast<char*>(&sizeName), sizeof(char));
			char aux;
			for (unsigned int i =0; i< sizeName; i++){
				 namesTable->read(reinterpret_cast<char*>(&aux), sizeof(char));
				name.push_back(aux);
			}
			cout << "\t" << name << endl;

			docAnterior = docActual +1;
		}
	}
}


void Query::printDocumentsNames(vector<Match>* documents) {
	if(documents == NULL)
		cout << "No se encontraron resultados para su búsqueda."
				"Verifique haber ingresado las palabras correctamente." << endl;
	else {
		unsigned int docActual;
		unsigned int docAnterior = 0;
		int diff;
		unsigned short int docLeido;
		uint8_t sizeName;
		string name;
		namesTable->seekg(0,namesTable->beg); //me muevo al principio

		cout << "\nSu búsqueda se encuentra en los siguientes documentos:" << endl;
		for(unsigned int i=0; i<documents->size(); i++) {
			docActual = documents->at(i).document; // resto uno porque empezamos a contar los documentos desde el 1, no desde el 0
			diff = docActual - docAnterior;
			name = string("");
			if(diff<0) {
				namesTable->seekg(0,namesTable->beg); //me muevo al principio
				diff = docActual;
			}
			advanceNDocumentsNames(diff);
			namesTable->read(reinterpret_cast<char*>(&docLeido), sizeof(unsigned short int));
			namesTable->read(reinterpret_cast<char*>(&sizeName), sizeof(char));
			char aux;
			for (unsigned int i =0; i< sizeName; i++){
				 namesTable->read(reinterpret_cast<char*>(&aux), sizeof(char));
				name.push_back(aux);
			}
			string veces = " ve";
			if(documents->at(i).long_match == 1)
				veces.append("z)");
			else
				veces.append("ces)");
			cout << "\t" << name << " \t(" << documents->at(i).long_match << veces <<endl;

			docAnterior = docActual +1;
		}
	}
}


void Query::advanceNDocumentsNames(int pos) {
	uint8_t size;
	for(int i=0; i<pos-1; i++) {
		if(!namesTable->eof()) {
			namesTable->seekg(sizeof(unsigned short int), namesTable->cur);
			namesTable->read(reinterpret_cast<char*>(&size), sizeof(char));
			namesTable->seekg(size, namesTable->cur);
		}
	}
}

